/*
 * Copyright (c) 2003-2004, Inversoft, All Rights Reserved
 *
 * This software is distribuable under the GNU Lesser General Public License.
 * For more information visit gnu.org.
 */
package org.emergent.antbite.savant.ant.taskdefs;


import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Ant;
import org.apache.tools.ant.taskdefs.Property;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Mapper;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.IdentityMapper;


/**
 * <p>
 * Run an Ant task several times, setting a param from a
 * tokenized list or fileset each time.
 * </p>
 *
 * <ul>
 * <li>target - target to be called, like antcall. Required.</li>
 * <li>property - name of property to set to value on each call.
 * Required.</li>
 * <li>mappedproperty - name of property to be set to mapped
 * value on each call.</li>
 * <li>delims - delimiters for tokenizing values.  Defaults
 * to "\t\n, ".</li>
 * <li>list - a delimited list of values.  target is called
 * once with each of these values setting property.</li>
 * <li>fileset - target is called once for each file, with
 * property set to the full path to the file.</li>
 * <li>dirset - target is called once for each file, with
 * property set to the full path to the directory.</li>
 * </ul>
 *
 * <p>
 * An optional mapper is allowed, the default is Identity.
 * If specified, then target is only called for those values
 * or files which match the mapper, and mappedproperty is
 * set to the result "to" value on each call.  Works for
 * both filesets and values.
 * </li>
 *
 * @author  Brian Pontarelli
 */
public class ForeachTask extends Task {

    private String target;
    private boolean inheritAll = true;
    private boolean inheritRefs = false;
    private String delims = ", \t\n";
    private String property = null;
    private String mappedProperty = null;
    private String list = null;
    private final List filesets = new ArrayList();
    private final List dirsets = new ArrayList();
    private Mapper mapperElement;
    private int verbosity = Project.MSG_VERBOSE;
    private Ant antTarget;


    /**
     * Set target to call.
     */
    public void setTarget(String target) {
        this.target = target;
    }

    /**
     * Sets the inheritAll property
     */
    public void setInheritAll(boolean inheritAll) {
        this.inheritAll = inheritAll;
    }

    /**
     * Sets the inheritRefs property
     */
    public void setInheritRefs(boolean inheritRefs) {
        this.inheritRefs = inheritRefs;
    }

    /**
     * Sets the property to be set with each call.
     */
    public void setProperty(String property) {
        this.property = property;
    }

    /**
     * Sets the property to be set with each call.
     */
    public void setMappedproperty(String property) {
        this.mappedProperty = property;
    }

    /**
     * Sets the list of values to iterate over
     */
    public void setList(String list) {
        this.list = list;
    }

    /**
     * Sets tokens.
     */
    public void setDelims(String delims) {
        this.delims = delims;
    }

    /**
     * Adds a set of files (nested fileset attribute).
     */
    public void addFileset(FileSet set) {
        filesets.add(set);
    }

    /**
     * Create param.
     */
    public Property createParam() {
        return antTarget.createProperty();
    }

    /**
     * Adds a new nested dirset
     */
    public void addDirset(FileSet set) {
        dirsets.add(set);
    }

    /**
     * Creates a new nested mapper
     */
    public Mapper createMapper() throws BuildException {
        if (mapperElement != null) {
            throw new BuildException("Cannot define more than one mapper",
                getLocation());
        } else {
            mapperElement = new Mapper(getProject());
            return mapperElement;
        }
    }

    /**
     * Used to force reporting of each call
     */
    public void setVerbose(boolean verbose) {
        this.verbosity =  verbose ? Project.MSG_INFO : Project.MSG_VERBOSE;
    }

    /**
     * Initializes the Ant target that we will be calling during the loops
     */
    public void init() {
        antTarget = (Ant) getProject().createTask("ant");
        antTarget.setOwningTarget(super.getOwningTarget());
        antTarget.setTaskName(getTaskName());
        antTarget.setLocation(getLocation());
        antTarget.init();
    }

    /**
     * Do the looping
     */
    public void execute() throws BuildException {

        // Must have a sub target, property and some vals
        if (target == null) {
            throw new BuildException("Attribute target is required.", getLocation());
        }

        if (property == null && mappedProperty == null) {
            throw new BuildException( "Specify a property or mappedproperty name.",
                getLocation());
        }

        if (!(list == null ^ (filesets.size() == 0 && dirsets.size() == 0))) {
            throw new BuildException("Specify either list or a fileset.", getLocation());
        }

        antTarget.setInheritAll(inheritAll);
        antTarget.setInheritRefs(inheritRefs);
        antTarget.setDir(getProject().getBaseDir());
        antTarget.setAntfile(getProject().getProperty("ant.file"));
        antTarget.setTarget(target);
        Project proj = antTarget.getProject();

        // Set up mapper
        FileNameMapper mapper = null;
        if (mapperElement != null) {
            mapper = mapperElement.getImplementation();
        } else {
            mapper = new IdentityMapper();
        }

        // Loop for tokenized values
        if (list != null) {
            for (StringTokenizer st = new StringTokenizer(list, delims);
                    st.hasMoreTokens(); ) {
                String val = st.nextToken();
                String[] val2 = mapper.mapFileName(val);

                callTarget(val, val2, antTarget, proj, target);
            }
        }

        // loop for fileset
        if (filesets.size() != 0) {

            // Walk thru the filesets
            for (Iterator sets = filesets.iterator(); sets.hasNext() ;) {
                FileSet fs = (FileSet) sets.next();
                File fromDir = fs.getDir(getProject());
                ArrayList fromFiles = new ArrayList();

                fromFiles.addAll(
                    Arrays.asList(fs.getDirectoryScanner(getProject()).getIncludedFiles()));

                for (Iterator files = fromFiles.iterator(); files.hasNext();) {
                    String f = (String) files.next();
                    String val = new File(fromDir, f).getPath();
                    String[] val2 = mapper.mapFileName(val);

                    callTarget(val, val2, antTarget, proj, target);
                }
            }
        }

        // loop for dirset
        if (dirsets.size() != 0) {

            // Walk thru the dirsets
            for (Iterator sets = dirsets.iterator(); sets.hasNext() ;) {
                FileSet fs = (FileSet) sets.next();
                File fromDir = fs.getDir(getProject());
                ArrayList fromFiles = new ArrayList();

                fromFiles.addAll(
                    Arrays.asList(fs.getDirectoryScanner(getProject()).getIncludedDirectories()));

                for (Iterator files = fromFiles.iterator(); files.hasNext(); ) {
                    String f = (String) files.next();
                    String val = new File(fromDir, f).getPath();
                    String[] val2 = mapper.mapFileName(val);

                    callTarget(val, val2, antTarget, proj, target);
                }
            }
        }
    }

    /**
     * Calls the ant target that the loop is setup to call
     */
    protected void callTarget(String val, String[] val2, Ant antTarget,
            Project proj, String target) {

        if (val2 == null || val2.length == 0) {
            log("Skipping unmapped " + val, verbosity);
            return;
        }

        for (int j = 0; j < val2.length; ++j) {
            if (property != null) {
                proj.setProperty(property, val);
            }

            if (mappedProperty != null) {
                proj.setProperty(mappedProperty, val2[j]);
            }

            log( "Calling " + target + " with "
                 + ( property == null ? "" :
                     property + "=" + proj.getProperty( property )
                     + ( mappedProperty == null ? "" : " and " ) )
                 + ( mappedProperty == null ? "" :
                     mappedProperty + "=" + proj.getProperty( mappedProperty ) ),
                 verbosity );

            antTarget.execute();
        }
    }
}